#include "SkGLDevice.h"
#include "SkGL.h"
#include "SkDrawProcs.h"
#include "SkRegion.h"
#include "SkThread.h"

static void TRACE_DRAW(const char func[], SkGLDevice* device,
                       const SkDraw& draw) {
    //    SkDebugf("--- <%s> %p %p\n", func, canvas, draw.fDevice);
}

struct SkGLDrawProcs : public SkDrawProcs {
public:
    void init(const SkRegion* clip, int height) {
        fCurrQuad = 0;
        fCurrTexture = 0;
        fClip = clip;
        fViewportHeight = height;

        glEnableClientState(GL_TEXTURE_COORD_ARRAY);
        glTexCoordPointer(2, SK_TextGLType, 0, fTexs);
        glDisableClientState(GL_COLOR_ARRAY);
        glVertexPointer(2, SK_TextGLType, 0, fVerts);
        
        fCtx = SkGetGLContext();
    }

    void* ctx() const { return fCtx; }
    GLenum texture() const { return fCurrTexture; }

    void flush() {
        if (fCurrQuad && fCurrTexture) {
            this->drawQuads();
        }
        fCurrQuad = 0;
    }

    void addQuad(GLuint texture, int x, int y, const SkGlyph& glyph,
                 SkFixed left, SkFixed right, SkFixed bottom) {
        SkASSERT((size_t)fCurrQuad <= SK_ARRAY_COUNT(fVerts));
        
        if (fCurrTexture != texture || fCurrQuad == SK_ARRAY_COUNT(fVerts)) {
            if (fCurrQuad && fCurrTexture) {
                this->drawQuads();
            }
            fCurrQuad = 0;
            fCurrTexture = texture;
        }
        
        fVerts[fCurrQuad].setIRectFan(x, y,
                                      x + glyph.fWidth, y + glyph.fHeight);
        fTexs[fCurrQuad].setXRectFan(left, 0, right, bottom);
        fCurrQuad += 4;
    }
    
    void drawQuads();

private:
    enum {
        MAX_QUADS = 32
    };
    
    SkGLTextVertex fVerts[MAX_QUADS * 4];
    SkGLTextVertex fTexs[MAX_QUADS * 4];
    
    // these are initialized in setupForText
    GLuint          fCurrTexture;    
    int             fCurrQuad;
    int             fViewportHeight;
    const SkRegion* fClip;
    void*           fCtx;
};

///////////////////////////////////////////////////////////////////////////////

SkGLDevice::SkGLDevice(const SkBitmap& bitmap, bool offscreen)
        : SkDevice(bitmap), fClipIter(bitmap.height()) {
    fDrawProcs = NULL;
}

SkGLDevice::~SkGLDevice() {
    if (fDrawProcs) {
        SkDELETE(fDrawProcs);
    }
}

void SkGLDevice::setMatrixClip(const SkMatrix& matrix, const SkRegion& clip) {
    this->INHERITED::setMatrixClip(matrix, clip);
    
    fGLMatrix.set(matrix);
    fMatrix = matrix;
    fClip = clip;
    fDirty = true;
}

SkGLDevice::TexOrientation SkGLDevice::bindDeviceAsTexture() {
    return kNo_TexOrientation;
}

void SkGLDevice::gainFocus(SkCanvas* canvas) {
    this->INHERITED::gainFocus(canvas);

    const int w = this->width();
    const int h = this->height();
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    SkGL::Ortho(0, w, h, 0, -1, 1);
    glMatrixMode(GL_MODELVIEW);
    fDirty = true;    
}

SkGLClipIter* SkGLDevice::updateMatrixClip() {
    bool useIter = false;

    // first handle the clip
    if (fDirty || !fClip.isRect()) {
        fClipIter.reset(fClip);
        useIter = true;
    } else if (fDirty) {
        // no iter means caller is not respecting complex clips :(
        SkGL::Scissor(fClip.getBounds(), this->height());
    }
    // else we're just a rect, and we've already call scissor

    // now handle the matrix
    if (fDirty) {
        MAKE_GL(glLoadMatrix)(fGLMatrix.fMat);
#if 0
        SkDebugf("--- gldevice update matrix %p %p\n", this, fFBO);
        for (int y = 0; y < 4; y++) {
            SkDebugf(" [ ");
            for (int x = 0; x < 4; x++) {
                SkDebugf("%g ", fGLMatrix.fMat[y*4 + x]);
            }
            SkDebugf("]\n");
        }
#endif
        fDirty = false;
    }

    return useIter ? &fClipIter : NULL;
}

///////////////////////////////////////////////////////////////////////////////

// must be in the same order as SkXfermode::Coeff in SkXfermode.h
SkGLDevice::AutoPaintShader::AutoPaintShader(SkGLDevice* device,
                                             const SkPaint& paint) {
    fDevice = device;
    fTexCache = device->setupGLPaintShader(paint);
}

SkGLDevice::AutoPaintShader::~AutoPaintShader() {
    if (fTexCache) {
        SkGLDevice::UnlockTexCache(fTexCache);
    }
}

SkGLDevice::TexCache* SkGLDevice::setupGLPaintShader(const SkPaint& paint) {
    SkGL::SetPaint(paint);
    
    SkShader* shader = paint.getShader();
    if (NULL == shader) {
        return NULL;
    }
    
    if (!shader->setContext(this->accessBitmap(false), paint, this->matrix())) {
        return NULL;
    }
    
    SkBitmap bitmap;
    SkMatrix matrix;
    SkShader::TileMode tileModes[2];
    if (!shader->asABitmap(&bitmap, &matrix, tileModes)) {
        return NULL;
    }
    
    bitmap.lockPixels();
    if (bitmap.getPixels() == NULL) {
        return NULL;
    }
    
    // see if we've already cached the bitmap from the shader
    SkPoint max;
    GLuint name;
    TexCache* cache = SkGLDevice::LockTexCache(bitmap, &name, &max);
    // the lock has already called glBindTexture for us
    SkGL::SetTexParams(paint.isFilterBitmap(), tileModes[0], tileModes[1]);
    
    // since our texture coords will be in local space, we wack the texture
    // matrix to map them back into 0...1 before we load it
    SkMatrix localM;
    if (shader->getLocalMatrix(&localM)) {
        SkMatrix inverse;
        if (localM.invert(&inverse)) {
            matrix.preConcat(inverse);
        }
    }
    
    matrix.postScale(max.fX / bitmap.width(), max.fY / bitmap.height());
    glMatrixMode(GL_TEXTURE);
    SkGL::LoadMatrix(matrix);
    glMatrixMode(GL_MODELVIEW);
    
    // since we're going to use a shader/texture, we don't want the color,
    // just its alpha
    SkGL::SetAlpha(paint.getAlpha());
    // report that we have setup the texture
    return cache;
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////

void SkGLDevice::drawPaint(const SkDraw& draw, const SkPaint& paint) {
    TRACE_DRAW("coreDrawPaint", this, draw);
    
    AutoPaintShader   shader(this, paint);
    SkGLVertex        vertex[4];
    const SkGLVertex* texs = shader.useTex() ? vertex : NULL;
    
    // set vert to be big enough to fill the space, but not super-huge, to we
    // don't overflow fixed-point implementations
    {
        SkRect r;
        r.set(this->clip().getBounds());
        SkMatrix inverse;
        if (draw.fMatrix->invert(&inverse)) {
            inverse.mapRect(&r);
        }
        vertex->setRectFan(r);
    }
    
    SkGL::DrawVertices(4, GL_TRIANGLE_FAN, vertex, texs, NULL, NULL,
                       this->updateMatrixClip());
}

static const GLenum gPointMode2GL[] = {
    GL_POINTS,
    GL_LINES,
    GL_LINE_STRIP
};

void SkGLDevice::drawPoints(const SkDraw& draw, SkCanvas::PointMode mode,
                            size_t count, const SkPoint pts[], const SkPaint& paint) {
    TRACE_DRAW("coreDrawPoints", this, draw);
    
    SkScalar width = paint.getStrokeWidth();
    if (width < 0) {
        return;
    }
    
    /*  We should really only use drawverts for hairlines, since gl and skia
     treat the thickness differently...
     */
    
    AutoPaintShader shader(this, paint);
    
    if (width <= 0) {
        width = SK_Scalar1;
    }
    
    if (SkCanvas::kPoints_PointMode == mode) {
        glPointSize(SkScalarToFloat(width));
    } else {
        glLineWidth(SkScalarToFloat(width));
    }
    
    const SkGLVertex* verts;
    
#if GLSCALAR_IS_SCALAR
    verts = (const SkGLVertex*)pts;
#else
    SkAutoSTMalloc<32, SkGLVertex> storage(count);
    SkGLVertex* v = storage.get();
    
    v->setPoints(pts, count);
    verts = v;
#endif
    
    const SkGLVertex* texs = shader.useTex() ? verts : NULL;
    
    SkGL::DrawVertices(count, gPointMode2GL[mode], verts, texs, NULL, NULL,
                       this->updateMatrixClip());
}

void SkGLDevice::drawRect(const SkDraw& draw, const SkRect& rect,
                          const SkPaint& paint) {
    TRACE_DRAW("coreDrawRect", this, draw);
    
    if (paint.getStyle() == SkPaint::kStroke_Style) {
        return;
    }
    
    if (paint.getStrokeJoin() != SkPaint::kMiter_Join) {
        SkPath  path;
        path.addRect(rect);
        this->drawPath(draw, path, paint);
        return;
    }
    
    AutoPaintShader shader(this, paint);
    
    SkGLVertex vertex[4];
    vertex->setRectFan(rect);
    const SkGLVertex* texs = shader.useTex() ? vertex : NULL;
    
    SkGL::DrawVertices(4, GL_TRIANGLE_FAN, vertex, texs, NULL, NULL,
                       this->updateMatrixClip());
}

void SkGLDevice::drawPath(const SkDraw& draw, const SkPath& path,
                          const SkPaint& paint) {
    TRACE_DRAW("coreDrawPath", this, draw);
    if (paint.getStyle() == SkPaint::kStroke_Style) {
        return;
    }
    
    AutoPaintShader shader(this, paint);
    
    SkGL::FillPath(path, paint, shader.useTex(), this->updateMatrixClip());
}

void SkGLDevice::drawBitmap(const SkDraw& draw, const SkBitmap& bitmap,
                            const SkMatrix& m, const SkPaint& paint) {
    TRACE_DRAW("coreDrawBitmap", this, draw);
    
    SkAutoLockPixels alp(bitmap);
    if (bitmap.getPixels() == NULL) {
        return;
    }
    
    SkGLClipIter* iter = this->updateMatrixClip();
    
    SkPoint max;
    GLenum name;
    SkAutoLockTexCache(bitmap, &name, &max);
    // the lock has already called glBindTexture for us
    SkGL::SetTexParamsClamp(paint.isFilterBitmap());
    
    glMatrixMode(GL_TEXTURE);
    glLoadIdentity();
    glMatrixMode(GL_MODELVIEW);
    glPushMatrix();
    SkGL::MultMatrix(m);
    
    SkGLVertex  pts[4], tex[4];
    
    pts->setIRectFan(0, 0, bitmap.width(), bitmap.height());
    tex->setRectFan(0, 0, max.fX, max.fY);
    
    // now draw the mesh
    SkGL::SetPaintAlpha(paint);
    glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
    
    SkGL::DrawVertices(4, GL_TRIANGLE_FAN, pts, tex, NULL, NULL, iter);
    
    glPopMatrix();    
}

// move this guy into SkGL, so we can call it from SkGLDevice
static void gl_drawSprite(int x, int y, int w, int h, const SkPoint& max,
                          const SkPaint& paint, SkGLClipIter* iter) {
    SkGL::SetTexParamsClamp(false);
    
    glMatrixMode(GL_TEXTURE);
    glLoadIdentity();
    glMatrixMode(GL_MODELVIEW);
    glPushMatrix();
    glLoadIdentity();
    
    SkGLVertex  pts[4], tex[4];
    
    // if h < 0, then the texture is bottom-to-top, but since our projection
    // matrix always inverts Y, we have to re-invert our texture coord here
    if (h < 0) {
        h = -h;
        tex->setRectFan(0, max.fY, max.fX, 0);
    } else {
        tex->setRectFan(0, 0, max.fX, max.fY);
    }
    pts->setIRectFan(x, y, x + w, y + h);
    
    SkGL::SetPaintAlpha(paint);
    glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
    
    // should look to use glDrawTexi() has we do for text...
    SkGL::DrawVertices(4, GL_TRIANGLE_FAN, pts, tex, NULL, NULL, iter);
    
    glPopMatrix();
}

void SkGLDevice::drawSprite(const SkDraw& draw, const SkBitmap& bitmap,
                            int left, int top, const SkPaint& paint) {
    TRACE_DRAW("coreDrawSprite", this, draw);
    
    SkAutoLockPixels alp(bitmap);
    if (bitmap.getPixels() == NULL) {
        return;
    }
    
    SkGLClipIter* iter = this->updateMatrixClip();
    
    SkPoint max;
    GLuint name;
    SkAutoLockTexCache(bitmap, &name, &max);    
    
    gl_drawSprite(left, top, bitmap.width(), bitmap.height(), max, paint, iter);
}

void SkGLDevice::drawDevice(const SkDraw& draw, SkDevice* dev,
                            int x, int y, const SkPaint& paint) {
    TRACE_DRAW("coreDrawDevice", this, draw);
    
    SkGLDevice::TexOrientation to = ((SkGLDevice*)dev)->bindDeviceAsTexture();
    if (SkGLDevice::kNo_TexOrientation != to) {
        SkGLClipIter* iter = this->updateMatrixClip();
        
        const SkBitmap& bm = dev->accessBitmap(false);
        int w = bm.width();
        int h = bm.height();
        SkPoint max;
        
        max.set(SkFixedToScalar(w << (16 - SkNextLog2(bm.rowBytesAsPixels()))),
                SkFixedToScalar(h << (16 - SkNextLog2(h))));
        
        if (SkGLDevice::kBottomToTop_TexOrientation == to) {
            h = -h;
        }
        gl_drawSprite(x, y, w, h, max, paint, iter);
    }
}

///////////////////////////////////////////////////////////////////////////////

static const GLenum gVertexModeToGL[] = {
    GL_TRIANGLES,       // kTriangles_VertexMode,
    GL_TRIANGLE_STRIP,  // kTriangleStrip_VertexMode,
    GL_TRIANGLE_FAN     // kTriangleFan_VertexMode
};

#include "SkShader.h"

void SkGLDevice::drawVertices(const SkDraw& draw, SkCanvas::VertexMode vmode,
                              int vertexCount, const SkPoint vertices[],
                              const SkPoint texs[], const SkColor colors[],
                              SkXfermode* xmode,
                              const uint16_t indices[], int indexCount,
                              const SkPaint& paint) {

    if (false) {
        SkRect bounds;
        SkIRect ibounds;
        
        bounds.set(vertices, vertexCount);
        bounds.round(&ibounds);
        
        SkDebugf("---- drawverts: %d pts, texs=%d colors=%d indices=%d bounds [%d %d]\n",
                 vertexCount, texs!=0, colors!=0, indexCount, ibounds.width(), ibounds.height());
    }
    
    SkGLClipIter* iter = this->updateMatrixClip();
    
    SkGL::SetPaint(paint);
    
    const SkGLVertex* glVerts;
    const SkGLVertex* glTexs = NULL;
    
#if GLSCALAR_IS_SCALAR
    glVerts = (const SkGLVertex*)vertices;
#else
    SkAutoSTMalloc<32, SkGLVertex> storage(vertexCount);
    storage.get()->setPoints(vertices, vertexCount);
    glVerts = storage.get();
#endif
    
    uint8_t* colorArray = NULL;
    if (colors) {
        colorArray = (uint8_t*)sk_malloc_throw(vertexCount*4);
        SkGL::SetRGBA(colorArray, colors, vertexCount);
    }
    SkAutoFree afca(colorArray);
    
    SkGLVertex* texArray = NULL;
    TexCache* cache = NULL;

    if (texs && paint.getShader()) {
        SkShader* shader = paint.getShader();
        
        //        if (!shader->setContext(this->accessBitmap(), paint, *draw.fMatrix)) {
        if (!shader->setContext(*draw.fBitmap, paint, *draw.fMatrix)) {
            goto DONE;
        }
        
        SkBitmap bitmap;
        SkMatrix matrix;
        SkShader::TileMode tileModes[2];
        if (shader->asABitmap(&bitmap, &matrix, tileModes)) {
            SkPoint max;
            GLuint name;
            cache = SkGLDevice::LockTexCache(bitmap, &name, &max);
            if (NULL == cache) {
                return;
            }

            matrix.postScale(max.fX / bitmap.width(), max.fY / bitmap.height());
            glMatrixMode(GL_TEXTURE);
            SkGL::LoadMatrix(matrix);
            glMatrixMode(GL_MODELVIEW);
            
#if GLSCALAR_IS_SCALAR
            glTexs = (const SkGLVertex*)texs;
#else
            texArray = (SkGLVertex*)sk_malloc_throw(vertexCount * sizeof(SkGLVertex));
            texArray->setPoints(texs, vertexCount);
            glTexs = texArray;
#endif
            
            SkGL::SetPaintAlpha(paint);
            SkGL::SetTexParams(paint.isFilterBitmap(),
                               tileModes[0], tileModes[1]);
        }
    }
DONE:
    SkAutoFree aftex(texArray);
    
    SkGL::DrawVertices(indices ? indexCount : vertexCount,
                       gVertexModeToGL[vmode],
                       glVerts, glTexs, colorArray, indices, iter);
    
    if (cache) {
        SkGLDevice::UnlockTexCache(cache);
    }
}

///////////////////////////////////////////////////////////////////////////////

#include "SkGlyphCache.h"
#include "SkGLTextCache.h"

static void SkGL_GlyphCacheAuxProc(void* data) {
    SkGLTextCache* cache = (SkGLTextCache*)data;

    SkDebugf("-------------- delete text texture cache, ctx=%p\n",
             cache->getCtx());
    SkDELETE((SkGLTextCache*)data);
}

#ifdef SK_SCALAR_IS_FIXED
#define SkDiv16ToScalar(numer, denom)    (SkIntToFixed(numer) / (denom))
#else
#define SkDiv16ToScalar(numer, denom)    SkScalarDiv(numer, denom)
#endif

// stolen from SkDraw.cpp - D1G_NoBounder_RectClip
static void SkGL_Draw1Glyph(const SkDraw1Glyph& state, const SkGlyph& glyph,
                            int x, int y) {
    SkASSERT(glyph.fWidth > 0 && glyph.fHeight > 0);

    SkGLDrawProcs* procs = (SkGLDrawProcs*)state.fDraw->fProcs;
    
    x += glyph.fLeft;
    y  += glyph.fTop;
    
    // check if we're clipped out (nothing to draw)
	SkIRect bounds;
	bounds.set(x, y, x + glyph.fWidth, y + glyph.fHeight);
    if (!SkIRect::Intersects(state.fClip->getBounds(), bounds)) {
        return;
    }
    
    // now dig up our texture cache
    
    SkGlyphCache* gcache = state.fCache;
    void* auxData;
    SkGLTextCache* textCache = NULL;
    
    if (gcache->getAuxProcData(SkGL_GlyphCacheAuxProc, &auxData)) {
        textCache = (SkGLTextCache*)auxData;
        if (textCache->getCtx() != procs->ctx()) {
            SkDebugf("------- textcache: old ctx %p new ctx %p\n",
                     textCache->getCtx(), procs->ctx());
            SkDELETE(textCache);
            textCache = NULL;
        }
    }
    if (NULL == textCache) {
        // need to create one
        textCache = SkNEW(SkGLTextCache);
        gcache->setAuxProc(SkGL_GlyphCacheAuxProc, textCache);
    }
    
    int offset;
    SkGLTextCache::Strike* strike = textCache->findGlyph(glyph, &offset);
    if (NULL == strike) {
        // make sure the glyph has an image
        uint8_t* aa = (uint8_t*)glyph.fImage;               
        if (NULL == aa) {
            aa = (uint8_t*)gcache->findImage(glyph);
            if (NULL == aa) {
                return; // can't rasterize glyph
            }
        }
        strike = textCache->addGlyphAndBind(glyph, aa, &offset);
        if (NULL == strike) {
            // too big to cache, need to draw as is...
            return;
        }
    }
    
    const int shiftW = strike->widthShift();
    const int shiftH = strike->heightShift();
    
    SkFixed left = offset << (16 - shiftW);
    SkFixed right = (offset + glyph.fWidth) << (16 - shiftW);
    SkFixed bottom = glyph.fHeight << (16 - shiftH);

    procs->addQuad(strike->texture(), x, y, glyph, left, right, bottom);
}

#if 1
// matches the orientation used in SkGL::setRectFan. Too bad we can't rely on
// QUADS in android's GL
static const uint8_t gQuadIndices[] = {
    0,   1,   2,   0,   2,   3,
    4,   5,   6,   4,   6,   7,
    8,   9,  10,   8,  10,  11,
    12,  13,  14,  12,  14,  15,
    16,  17,  18,  16,  18,  19,
    20,  21,  22,  20,  22,  23,
    24,  25,  26,  24,  26,  27,
    28,  29,  30,  28,  30,  31,
    32,  33,  34,  32,  34,  35,
    36,  37,  38,  36,  38,  39,
    40,  41,  42,  40,  42,  43,
    44,  45,  46,  44,  46,  47,
    48,  49,  50,  48,  50,  51,
    52,  53,  54,  52,  54,  55,
    56,  57,  58,  56,  58,  59,
    60,  61,  62,  60,  62,  63,
    64,  65,  66,  64,  66,  67,
    68,  69,  70,  68,  70,  71,
    72,  73,  74,  72,  74,  75,
    76,  77,  78,  76,  78,  79,
    80,  81,  82,  80,  82,  83,
    84,  85,  86,  84,  86,  87,
    88,  89,  90,  88,  90,  91,
    92,  93,  94,  92,  94,  95,
    96,  97,  98,  96,  98,  99,
    100, 101, 102, 100, 102, 103,
    104, 105, 106, 104, 106, 107,
    108, 109, 110, 108, 110, 111,
    112, 113, 114, 112, 114, 115,
    116, 117, 118, 116, 118, 119,
    120, 121, 122, 120, 122, 123,
    124, 125, 126, 124, 126, 127
};
#else
static void generateQuadIndices(int n) {
    int index = 0;
    for (int i = 0; i < n; i++) {
        SkDebugf("    %3d, %3d, %3d, %3d, %3d, %3d,\n",
                 index, index + 1, index + 2, index, index + 2, index + 3);
        index += 4;
    }
}
#endif

void SkGLDrawProcs::drawQuads() {
    SkASSERT(SK_ARRAY_COUNT(gQuadIndices) == MAX_QUADS * 6);

    glBindTexture(GL_TEXTURE_2D, fCurrTexture);

#if 0
    static bool gOnce;
    if (!gOnce) {
        generateQuadIndices(MAX_QUADS);
        gOnce = true;
    }
#endif

    // convert from quad vertex count to triangle vertex count
    // 6/4 * n == n + (n >> 1) since n is always a multiple of 4
    SkASSERT((fCurrQuad & 3) == 0);
    int count = fCurrQuad + (fCurrQuad >> 1);

    if (fClip->isComplex()) {
        SkGLClipIter iter(fViewportHeight);
        iter.reset(*fClip);
        while (!iter.done()) {
            iter.scissor();
            glDrawElements(GL_TRIANGLES, count, GL_UNSIGNED_BYTE, gQuadIndices);
            iter.next();
        }
    } else {
        glDrawElements(GL_TRIANGLES, count, GL_UNSIGNED_BYTE, gQuadIndices);
    }
}

void SkGLDevice::setupForText(SkDraw* draw, const SkPaint& paint) {
    // we handle complex clips in the SkDraw common code, so we don't check
    // for it here
    this->updateMatrixClip();
    
    SkGL::SetPaint(paint, false);
    
    glMatrixMode(GL_TEXTURE);
    glLoadIdentity();
    
    glMatrixMode(GL_MODELVIEW);
    glPushMatrix();
    glLoadIdentity();

    // deferred allocation
    if (NULL == fDrawProcs) {
        fDrawProcs = SkNEW(SkGLDrawProcs);
        fDrawProcs->fD1GProc = SkGL_Draw1Glyph;
    }

    // init our (and GL's) state
    fDrawProcs->init(draw->fClip, this->height());
    // assign to the caller's SkDraw
    draw->fProcs = fDrawProcs;

    glEnable(GL_TEXTURE_2D);
    glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
    glShadeModel(GL_FLAT); 
}

void SkGLDevice::drawText(const SkDraw& draw, const void* text,
                          size_t byteLength, SkScalar x, SkScalar y,
                          const SkPaint& paint) {
    /*  Currently, perspective text is draw via paths, invoked directly by
     SkDraw. This can't work for us, since the bitmap that our draw points
     to has no pixels, so we just abort if we're in perspective.
     
     Better fix would be to...
     - have a callback inside draw to handle path drawing
     - option to have draw call the font cache, which we could patch (?)
     */
    if (draw.fMatrix->getType() & SkMatrix::kPerspective_Mask) {
        return;
    }
    
    SkDraw myDraw(draw);
    this->setupForText(&myDraw, paint);
    this->INHERITED::drawText(myDraw, text, byteLength, x, y, paint);
    fDrawProcs->flush();
    glPopMatrix();  // GL_MODELVIEW
}

void SkGLDevice::drawPosText(const SkDraw& draw, const void* text,
                             size_t byteLength, const SkScalar pos[],
                             SkScalar constY, int scalarsPerPos,
                             const SkPaint& paint) {
    if (draw.fMatrix->getType() & SkMatrix::kPerspective_Mask) {
        return;
    }
    
    SkDraw myDraw(draw);
    this->setupForText(&myDraw, paint);
    this->INHERITED::drawPosText(myDraw, text, byteLength, pos, constY,
                                 scalarsPerPos, paint);
    fDrawProcs->flush();
    glPopMatrix();  // GL_MODELVIEW
}

void SkGLDevice::drawTextOnPath(const SkDraw& draw, const void* text,
                                size_t byteLength, const SkPath& path,
                                const SkMatrix* m, const SkPaint& paint) {
    // not supported yet
}

